package com.t.bricks.bussiness.util.lucene;

import java.io.IOException;
import java.nio.file.Paths;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.document.LongPoint;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryparser.classic.MultiFieldQueryParser;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.FSDirectory;

import com.t.bricks.model.Log;
import com.t.bricks.model.MsgEmity;
import com.t.bricks.model.Page;
import com.t.bricks.utils.function.json.JacksonShell;

/**
 * 对指定数据类型进行搜索引擎操作
 */
public class LuceneFactory {
	
	/**
	 * 自增编号,负数表示还没初始化
	 */
	private static int iAutoNumber = 1;
	
	/**
	 * 取自增编号
	 * @return
	 */
	private static synchronized String getAutoNumber() {
		String result = String.format("%s%03d", LocalDateTime.now().toString(), iAutoNumber++);
		
		if (iAutoNumber > 999) {
			iAutoNumber = 1;
		}
		
		return result;
	}

	/**
	 * 对象转索引文档
	 * @param idName 索引库中的id对应的字段属性名,如果没有则自创
	 * @param obj 要转换的数据对象
	 * @return
	 */
	public static Document toDocument(String idName, Object obj) {
		if (null == obj) {
			return null;
		}
		
		Document document = new Document();// Lucene存储的格式(Map装的k,v)
		
		Map<String, Object> json = JacksonShell.toJavaObject(obj, Map.class);
		//JSONObject json = (JSONObject) JSON.toJSON(obj);
		for (Map.Entry<String, Object> entry : json.entrySet()) {
			document.add(new TextField(entry.getKey(), String.valueOf(entry.getValue()), Store.YES));//TextField默认分词
        }

		String idValue = String.valueOf(json.get(idName));
		if (null == idValue) {
			idValue = getAutoNumber();
		}

		// 向文档中添加一个字符串类型的属性，建立索引,Field.Store.YES：把文件名存索引文件里，为NO就说明不需要加到索引文件里去
		document.add(new TextField("id", idValue, Store.NO));
		// 在文档中存储
		//document.add(new StoredField("id", idValue));
		document.add(new TextField("contents", JacksonShell.toJSONString(json), Store.YES));//先转换成json字符串再保存

		return document;
	}
	
	/**
	 * 索引文档转换成对象
	 * @param document
	 * @return
	 */
	public static <T> T toObj(Document document, Class<T> clazz) {
		if (null == document) {
			return null;
		}
		
		if (null == clazz) {
			return null;
		}
		
		return JacksonShell.toJavaObject(document.get("contents"), clazz);
		//return JSON.parseObject(document.get("contents"), clazz);
	}

	/**
	 * 批量添加数据
	 * @param idName 索引库中的id对应的字段属性名,如果没有则自创
	 * @param list 数据为E的类型
	 * @param core 库
	 * @return
	 */
	public static MsgEmity add(String idName, List<?> list, String core) {
		IndexWriter indexWriter = null;
		try {
			FSDirectory fsDirectory = FSDirectory.open(Paths.get(core));
			// 创建一个标准分词器，一个字分一次
			Analyzer analyzer = new StandardAnalyzer();
			// 写入索引的配置，设置了分词器
			IndexWriterConfig indexWriterConfig = new IndexWriterConfig(analyzer);
			// 指定了写入数据目录和配置
			indexWriter = new IndexWriter(fsDirectory, indexWriterConfig);
			
			for (Object object : list) {
				Document document = toDocument(idName, object);// 创建一个文档对象
				indexWriter.addDocument(document);// 通过IndexWriter写入
			}
			
			indexWriter.close();
		} catch (Exception e) {
			Log.error("添加数据发生异常:", e.getMessage());
			if (null != indexWriter) {
				try {
					indexWriter.rollback();
				} catch (IOException e1) {
					Log.error("添加数据发生异常后,执行回滚操作发生2次异常:", e.getMessage());
				}
			}
			
			return new MsgEmity(false, "添加数据失败", 1990);
		}
		
		return new MsgEmity(true, "添加成功", 1999);
	}

	/**
	 * 添加数据
	 * @param document
	 * @param core 库
	 * @return
	 */
	public static MsgEmity add(Document document, String core) {
		if (null == document) {
			return new MsgEmity(false, "添加数据失败", 1990);	
		}
		
		try {
			FSDirectory fsDirectory = FSDirectory.open(Paths.get(core));
			// 创建一个标准分词器，一个字分一次
			Analyzer analyzer = new StandardAnalyzer();
			// 写入索引的配置，设置了分词器
			IndexWriterConfig indexWriterConfig = new IndexWriterConfig(analyzer);
			// 指定了写入数据目录和配置
			IndexWriter indexWriter = new IndexWriter(fsDirectory, indexWriterConfig);
			
			// 通过IndexWriter写入
			indexWriter.addDocument(document);
			indexWriter.close();
		} catch (Exception e) {
			Log.error("添加数据发生异常:", e.getMessage());
			return new MsgEmity(false, "添加数据失败", 1991);
		}
		
		return new MsgEmity(true, "添加成功", 1999);
	}
	
	/**
	 * 添加数据
	 * @param idName 索引库中的id对应的字段属性名,如果没有则自创
	 * @param obj
	 * @param core 库
	 * @return
	 */
	public static MsgEmity add(String idName, Object obj, String core) {
		Document document = toDocument(idName, obj);// 创建一个文档对象
		
		return add(document, core);
	}

//	/**
//	 * @Title: 查询数据
//	 * @methodName:Search
//	 * @Description:
//	 */
//	public MsgEmity search() {
//		Analyzer analyzer = new StandardAnalyzer();
//		DirectoryReader directoryReader = null;
//
//		try {
//			directoryReader = DirectoryReader.open(FSDirectory.open(Paths.get(getCore())));// 得到读取索引文件的路径
//			IndexSearcher indexSearcher = new IndexSearcher(directoryReader);// 建立索引查询器
//			String queryStr = "数据";
//			// 创建一个查询条件解析器
//			QueryParser parser = new QueryParser("content", analyzer);
//			// 对查询条件进行解析
//			Query query = parser.parse(queryStr);
//
//			TopDocs topDocs = indexSearcher.search(query, 10);
//
//			// 获取到查找到的文文档ID和得分
//			List<Document> list = new ArrayList<Document>();
//			ScoreDoc[] scoreDocs = topDocs.scoreDocs;
//			for (ScoreDoc scoreDoc : scoreDocs) {
//				// 从索引中查询到文档的ID，
//				int doc = scoreDoc.doc;
//				// 在根据ID到文档中查找文档内容
//				Document document = indexSearcher.doc(doc);
//				list.add(document);
//				// 将文档转换成对应的实体类
//				// Article article = Article.parseArticle(document);
//			}
//
//			reader.close();
//		} catch (IOException e) {
//			e.printStackTrace();
//		} finally {
//			closeDirectoryReader(directoryReader);
//		}
//
//		return new MsgEmity(true, "查询成功", list);
//	}

	/**
	 * 删除数据
	 * @param idName
	 * @param idValue
	 * @param core 库
	 * @return
	 */
	public static MsgEmity del(String idName, String idValue, String core) {
		Analyzer analyzer = new StandardAnalyzer();
		
		FSDirectory fsDirectory = null;
		try {
			fsDirectory = FSDirectory.open(Paths.get(core));// 得到读取索引文件的路径
			IndexWriterConfig indexWriterConfig = new IndexWriterConfig(analyzer);
			IndexWriter indexWriter = new IndexWriter(fsDirectory, indexWriterConfig);
			
			QueryParser parser = new QueryParser(idName, analyzer);// 第一个参数是要查询的字段；
			Query query = parser.parse(idValue);// 第二个参数是分析器Analyzer
			
			indexWriter.deleteDocuments(query);
			
			indexWriter.commit();
			indexWriter.close();
		} catch (Exception e) {
			Log.error("删除数据发生异常:", e.getMessage());
		} finally {
			closeFsDirectory(fsDirectory);
		}
		
		return new MsgEmity(true, "删除成功");
	}

	/**
	 * 删除指定字段值的数据
	 * @param keyName
	 * @param keyValue
	 * @param core 库
	 * @return
	 */
	public static MsgEmity delByKey(String keyName, String keyValue, String core) {
		Analyzer analyzer = new StandardAnalyzer();
		
		FSDirectory fsDirectory = null;
		try {
			fsDirectory = FSDirectory.open(Paths.get(core));// 得到读取索引文件的路径
			IndexWriterConfig indexWriterConfig = new IndexWriterConfig(analyzer);
			IndexWriter indexWriter = new IndexWriter(fsDirectory, indexWriterConfig);
			
			QueryParser parser = new QueryParser(keyName, analyzer);// 第一个参数是要查询的字段；
			Query query = parser.parse(keyValue);// 第二个参数是分析器Analyzer
			
			indexWriter.deleteDocuments(query);
			
			indexWriter.commit();
			indexWriter.close();
		} catch (Exception e) {
			Log.error("删除数据发生异常:", e.getMessage());
		} finally {
			closeFsDirectory(fsDirectory);
		}
		
		return new MsgEmity(true, "删除成功");
	}

	/**
	 * 更新数据
	 * 注意:lucene的update比较特殊，update的代价太高，其实是先删除，然后在插入
	 * @param idName
	 * @param idValue
	 * @param obj
	 * @param core 库
	 * @return
	 */
	public static MsgEmity update(String idName, String idValue, Object obj, String core) {
		if (null == core || "".equals(core.trim())) {
			return MsgEmity.err(1001, "库参数为空");
		}
		
		FSDirectory fsDirectory = null;
		try {
			StandardAnalyzer analyzer = new StandardAnalyzer();
			fsDirectory = FSDirectory.open(Paths.get(core));
			IndexWriterConfig indexWriterConfig = new IndexWriterConfig(analyzer);
			IndexWriter indexWriter = new IndexWriter(fsDirectory, indexWriterConfig);
	
			Document document = toDocument(idValue, obj);
	
			indexWriter.updateDocument(new Term(idName, idValue), document);
	
			indexWriter.commit();
			indexWriter.close();
		} catch (Exception e) {
			Log.error("删除数据发生异常:", e.getMessage());
			return new MsgEmity(false, "更新失败", 1999);
		} finally {
			closeFsDirectory(fsDirectory);
		}

		return new MsgEmity(true, "更新成功", 1999);
	}

	/**
	 * 查询多个字段
	 * @param <T>
	 * @param fields {"字段名1", "字段名2"}
	 * @param keys 关键字 {"字段1关键字", "字段2关键字"}
	 * @param rels 条件关系 {"OR", "ANd", "NOT"}
	 * @param sortMap 排序信息集合[{"字段名1", 是否降序},{"字段名2", 是否降序}]
	 * @param page 分页信息
	 * @param clazz 返回的数据类型
	 * @param core 库
	 * @return
	 */
	public static <T> MsgEmity search(List<String> fields, List<String> keys,
			List<String> rels, Map<String, Boolean> sortMap, Page page,
			Class<T> clazz, String core) {
		LocalDateTime startTime = LocalDateTime.now();//搜索开始时间
		
		keys = removeEmpty(keys);
		if (null == keys || keys.size() < 0) {
			return new MsgEmity(false, "关键字为空", 1990);
		}
		
		fields = removeEmpty(fields);
		if (fields.size() != keys.size()) {
			return new MsgEmity(false, "关键字与检索字段不匹配", 1991);
		}

		rels = removeEmpty(rels);
		if (null == rels || rels.size() < 1) {
			rels = new ArrayList<String>();
			for (int i = 0; i < fields.size(); i++) {
				rels.add("or");
			}
		}
		
		if (keys.size() != fields.size() || keys.size() != rels.size()) {
			return new MsgEmity(false, "关键字、检索字段、条件关系数量不匹配", 1992);
		}
		
		for (int i = 0; i < keys.size(); i++) {
			keys.set(i, String.format("\"%s\"", keys.get(i)));//用引号将'"'词进行包装,防止分词
		}
		
		SortField[] sortFieldArray = null;
		if (null != sortMap) {
			sortFieldArray = new SortField[sortMap.size() +1];
			int i = 0;
			for (Map.Entry<String, Boolean> entry : sortMap.entrySet()) {
				sortFieldArray[i] = new SortField(entry.getKey(), SortField.Type.DOC, entry.getValue());//根据某个域（field）的名称构造SortField, reverse为false为升序
			}
			sortFieldArray[sortMap.size()] = new SortField("dCreateDate", SortField.Type.DOC, true);//就算没有'dCreateDate'这个字段也不会报错
		} else {
			sortFieldArray = new SortField[]{
				new SortField("dCreateDate", SortField.Type.DOC, true)//根据某个域（field）的名称构造SortField, reverse为false为升序
			};
		}

		String[] fieldsArray = fields.toArray(new String[1]); //返回一个包含所有对象的指定类型的数组
		String[] keysArray = keys.toArray(new String[1]); //返回一个包含所有对象的指定类型的数组
		Occur[] occ = strToOccur(rels);//Occur.MUST表示and,Occur.MUST_NOT表示not,Occur.SHOULD表示or.
		Sort sort = new Sort(sortFieldArray);//排序

		DirectoryReader directoryReader = null;
		try {
			directoryReader = DirectoryReader.open(FSDirectory.open(Paths.get(core)));
			IndexSearcher indexSearcher = new IndexSearcher(directoryReader);
			
			Analyzer analyzer = new StandardAnalyzer();
			Query query = MultiFieldQueryParser.parse(keysArray, fieldsArray, occ, analyzer);
			TopDocs topDocs = indexSearcher.search(query, page.getEndRow(), sort);//搜索的最大数量, 排序
			
			ScoreDoc[] scoreDocs = topDocs.scoreDocs;
			
			page.setCountRow(scoreDocs.length);// 搜索结果总数量
	        int end = Math.min(page.getEndRow(), scoreDocs.length);//查询终止记录位置

			List<T> result = new ArrayList<T>();
			for (int i = page.getStartRow() -1; i < end; i++) {
				ScoreDoc scoreDoc = scoreDocs[i];
				Document document = indexSearcher.doc(scoreDoc.doc);
				T object = toObj(document, clazz);
				if (null == object) {
					Log.error("数据转换错误,居然返回了null");
					continue;
				}
				
				result.add(object);
			}
			
			page.setRecordList(result);
		} catch (Exception e) {
			Log.error("查询数据发生异常:", e.getMessage());
			Duration duration = Duration.between(startTime, LocalDateTime.now());
			return MsgEmity.err(1001, "查询失败，搜索耗时：", String.valueOf(duration.toMillis() / 1000d), "秒");
		} finally {
			closeDirectoryReader(directoryReader);
		}

		Duration duration = Duration.between(startTime, LocalDateTime.now());
		
		return MsgEmity.success(page, "查询成功，搜索耗时：", String.valueOf(duration.toMillis() / 1000d), "秒");
	}
	
	/**
	 * 查询多个字段
	 * @param <T>
	 * @param keys 关键字 {"字段1关键字", "字段2关键字"}
	 * @param rels 条件关系 {"OR", "ANd", "NOT"}
	 * @param sortMap 排序信息集合 [{"字段名1", 是否降序},{"字段名2", 是否降序}]
	 * @param page 分页信息
	 * @param clazz 返回的数据类型
	 * @param core 库
	 * @return
	 */
	public static <T> MsgEmity search(List<String> keys, List<String> rels,
			Map<String, Boolean> sortMap, Page page, Class<T> clazz, String core) {
		keys = removeEmpty(keys);
		if (null == keys || keys.size() < 0) {
			return new MsgEmity(false, "关键字为空", 1990);
		}
		
		List<String> fields = new ArrayList<String>();
		for (int i = 0; i < keys.size(); i++) {
			fields.add("contents");
		}
		
		return search(fields, keys, rels, sortMap, page, clazz, core);
	}

	/**
	 * 查询多个字段
	 * @param <T>
	 * @param keys 关键字 {"字段1关键字", "字段2关键字"}
	 * @param sortMap 排序信息集合 [{"字段名1", 是否降序},{"字段名2", 是否降序}]
	 * @param page 分页信息
	 * @param clazz 返回的数据类型
	 * @param core 库
	 * @return
	 */
	public static <T> MsgEmity search(List<String> keys,
			Map<String, Boolean> sortMap, Page page, Class<T> clazz, String core) {
		return search(keys, null, sortMap, page, clazz, core);
	}

	/**
	 * 查询单个字段
	 * @param <T>
	 * @param field 待查字段
	 * @param key 关键字(只有一个),如果含有空格,则按or条件处理
	 * @param sortMap 排序信息集合 [{"字段名1", 是否降序},{"字段名2", 是否降序}]
	 * @param page 分页信息
	 * @param clazz 返回的数据类型
	 * @param core 库
	 * @return
	 */
	public static <T> MsgEmity search(String field, String key,
			Map<String, Boolean> sortMap, Page page, Class<T> clazz, String core) {
		if (null == key || "".equals(key.trim())) {
			return new MsgEmity(false, "查询关键字为空", 1991);
		}
		
		field = (null == field || "".equals(field.trim())) ? "contents" : field;
		
		String[] array = key.trim().split(" ");
		List<String> keys = new ArrayList<String>(Arrays.asList(array));

		List<String> fields = new ArrayList<String>();
		List<String> rels = new ArrayList<String>();
		for (int i = 0; i < array.length; i++) {
			rels.add("and");//按习惯用' '分割的文字作为and表示
			fields.add(field);
		}
		
		return search(fields, keys, rels, sortMap, page, clazz, core);
	}

	/**
	 * 查询contents字段
	 * @param <T>
	 * @param key 关键字(只有一个),如果含有空格,则按or条件处理
	 * @param sortMap 排序信息集合 [{"字段名1", 是否降序},{"字段名2", 是否降序}]
	 * @param page 分页信息
	 * @param clazz 返回的数据类型
	 * @param core 库
	 * @return
	 */
	public static <T> MsgEmity search(String key, Map<String, Boolean> sortMap,
			Page page, Class<T> clazz, String core) {
		return search(null, key, sortMap, page, clazz, core);
	}

	/**
	 * 查询contents字段
	 * @param <T>
	 * @param key 关键字(只有一个),如果含有空格,则按or条件处理
	 * @param page 分页信息
	 * @param clazz 返回的数据类型
	 * @param core 库
	 * @return
	 */
	public static <T> MsgEmity search(String key, Page page, Class<T> clazz, String core) {
		return search(null, key, null, page, clazz, core);
	}

	// /**
	// * @Title: 布尔查询，可以组合多个查询条件
	// * @methodName:BooleanQuery
	// * @Description:
	// */
	// public MsgEmity booleanQuery() throws Exception {
	//
	// //String indexPath = "D:\\usr\\lucene\\index";
	// DirectoryReader directoryReader =
	// DirectoryReader.open(FSDirectory.open(Paths.get(getCore())));
	// IndexSearcher indexSearcher = new IndexSearcher(directoryReader);
	//
	// Query query1 = new TermQuery(new Term("title", "老师"));
	// Query query2 = new TermQuery(new Term("content", "丁"));
	// BooleanClause bc1 = new BooleanClause(query1, BooleanClause.Occur.MUST);
	// BooleanClause bc2 = new BooleanClause(query2,
	// BooleanClause.Occur.MUST_NOT);
	// BooleanQuery boolQuery = new
	// BooleanQuery.Builder().add(bc1).add(bc2).build();
	// System.out.println(boolQuery);
	//
	// TopDocs topDocs = indexSearcher.search(boolQuery, 10);
	// ScoreDoc[] scoreDocs = topDocs.scoreDocs;
	// for (ScoreDoc scoreDoc : scoreDocs) {
	// int doc = scoreDoc.doc;
	// Document document = indexSearcher.doc(doc);
	// Article article = Article.parseArticle(document);
	// System.out.println(article);
	// }
	//
	// closeDirectoryReader(directoryReader);
	// }

	/**
	 * 范围查询
	 * @param core 库
	 * @return
	 */
	public static MsgEmity rangeQuery(String core) {
		List<Document> result = new ArrayList<Document>();

		DirectoryReader directoryReader = null;

		try {
			directoryReader = DirectoryReader.open(FSDirectory.open(Paths.get(core)));
			IndexSearcher indexSearcher = new IndexSearcher(directoryReader);

			Query query = LongPoint.newRangeQuery("id", 107L, 108L);

			TopDocs topDocs = indexSearcher.search(query, 10);
			ScoreDoc[] scoreDocs = topDocs.scoreDocs;

			for (ScoreDoc scoreDoc : scoreDocs) {
				int doc = scoreDoc.doc;
				Document document = indexSearcher.doc(doc);
				result.add(document);
			}

		} catch (Exception e) {

		} finally {
			closeDirectoryReader(directoryReader);
		}

		return new MsgEmity(true, "查询成功", result);
	}

	/**
	 * 关闭directoryReader
	 * @param directoryReader
	 */
	private static void closeDirectoryReader(DirectoryReader directoryReader) {
		if (null == directoryReader) {
			return;
		}

		try {
			directoryReader.close();
		} catch (Exception e) {
			Log.error("关闭索引库文件流发生异常:", e.getMessage());
		}
	}

	/**
	 * 关闭fsDirectory
	 * @param fsDirectory
	 */
	private static void closeFsDirectory(FSDirectory fsDirectory) {
		if (null == fsDirectory) {
			return;
		}

		try {
			fsDirectory.close();
		} catch (Exception e) {
			Log.error("关闭索引库文件流发生异常:", e.getMessage());
		}
	}
	
	/**
	 * 移除空数据
	 * @param array
	 * @return
	 */
	private static List<String> removeEmpty(List<String> list) {
		if (null == list || list.size() < 1) {
			return list;
		}
		
		for (int i = list.size() -1; i >= 0; i--) {
			if (null == list.get(i) || "".equals(list.get(i).trim())) {
				list.remove(i);
			}
		}

		return list;//list.toArray(new String[1]); //返回一个包含所有对象的指定类型的数组
	}
	
	/**
	 * 字符串转条件关系
	 * 注: Occur.MUST表示and,Occur.MUST_NOT表示not,Occur.SHOULD表示or.
	 * @param rels
	 * @return
	 */
	private static Occur[] strToOccur(List<String> rels) {
		if (null == rels || rels.size() < 1) {
			return null;
		}
		
		Occur[] result = new Occur[rels.size()];
		for (int i = 0; i < rels.size(); i++) {
			result[i] = strToOccur(rels.get(i));
		}
		
		return result;
	}
	
	/**
	 * 字符串转条件枚举
	 * @param rel
	 * @return
	 */
	private static Occur strToOccur(String rel) {
		if (null == rel || "".equals(rel.trim())) {
			return Occur.MUST;
		}
		
		switch (rel.trim().toLowerCase()) {
		case "and":
			return Occur.MUST;
		case "or":
			return Occur.SHOULD;
		case "not":
			return Occur.MUST_NOT;
		default:
			return Occur.MUST;
		}
	}
	
}
